﻿namespace EFS.RC.CC.Sessions.OfficeService.Customers
{
	/// <summary>
	/// Customer session
	/// </summary>
	internal class DirectDialIn : EFS.RC.CC.Sessions.OfficeService.Customers.Customer
	{
		#region Composition

		// Office conversation
		private Microsoft.Rtc.Collaboration.Conversation officeConversation;

		// Customer call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall inboundCustomerAudioVideoCall = null;

		// office call
		private Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall officeConversationAudioVideoCall = null;

		// back to back call into office conversation
		private Microsoft.Rtc.Collaboration.BackToBackCall officeConversationB2BCall = null;

		// presence
		private EFS.RC.CC.Presences.Operators.Presence operatorPresence = null;

		// Customer uri
		private System.String dialFromUri = null;

		// Impersonated uri
		private System.String participantEndPointUri = null;

		// Display name
		private System.String impersonatedDisplayName = null;

		// Id
		private System.Guid conversationGuid = System.Guid.NewGuid();

		#endregion

		#region Constructor

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="customersOfficeServiceEndPointApplication"></param>
		/// <param name="OutsideAudioVideoCall"></param>
		public DirectDialIn(
			EFS.RC.CC.Applications.EndPoints.OfficeService.Customers customersOfficeServiceEndPointApplication,
			Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall inboundCustomerAudioVideoCall,
			EFS.RC.CC.Presences.Operators.Presence operatorPresence, 
			System.String dialFromUri) : base(customersOfficeServiceEndPointApplication)
		{
			// check
			EFS.Common.Verify.Reference(inboundCustomerAudioVideoCall, "inboundCustomerAudioVideoCall");
			EFS.Common.Verify.Reference(operatorPresence, "operatorPresence");
			EFS.Common.Verify.String(dialFromUri, "dialFromUri");

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "DialIn:ctor", dialFromUri, GetType()); 

			// set locals
			this.inboundCustomerAudioVideoCall = inboundCustomerAudioVideoCall;
			this.operatorPresence = operatorPresence;
			this.dialFromUri = dialFromUri;

			// make display name
			impersonatedDisplayName = EFS.RC.Common.Utilities.SipUriCleanup(dialFromUri);

			// make impersonated uri
			participantEndPointUri = System.String.Format("sip:{0}@{1}", ConversationGuid, customersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint.DefaultDomain);

			// state change
			inboundCustomerAudioVideoCall.Conversation.StateChanged += OnCustomerConversationStateChanged;

			// impersonate
			inboundCustomerAudioVideoCall.Conversation.Impersonate(dialFromUri, null, impersonatedDisplayName);
		}

		#endregion

		#region Usage

		/// <summary>
		/// Describe this object into a nice formated string
		/// </summary>
		/// <returns></returns>
		public override System.String Describe()
		{
			return System.String.Format(System.String.Format("EFS.RC.CC.Sessions.Conversation.DirectDialIn [{0}] {1}", operatorPresence.Describe(), inboundCustomerAudioVideoCall.RemoteEndpoint.Participant.PhoneUri));
		}

		/// Display uri
		/// </summary>
		public override System.String DisplayUri
		{
			get { return impersonatedDisplayName; }
		}

		/// <summary>
		/// Impersonated display name
		/// </summary>
		public override System.String DisplayName
		{
			get { return impersonatedDisplayName; }
		}

		/// <summary>
		/// Impersonated uri
		/// </summary>
		public override System.String ParticipantEndPointUri
		{
			get { return participantEndPointUri; }
		}

		/// <summary>
		/// Customer uri
		/// </summary>
		public override System.String CustomerUri
		{
			get { return dialFromUri; }
		}

		#endregion

		#region Startup

		/// <summary>
		/// Mark start time
		/// </summary>
		/// <returns></returns>
		protected override System.DateTime MarkStart()
		{
			return EFS.RC.CC.Tools.Loggers.CHL.Add(ConversationGuid, CustomersOfficeServiceEndPointApplication.ApplicationPresence.PresenceEndPointDeployment.ID.Value, DisplayUri, null, null, null, null, null, EFS.RC.CC.Events.Conversation.Customer_StartIn);
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		protected override void SetupSession()
		{
			EFS.EventLog.Log.LogInfo("SetupSession", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "SetupSession", this);

			// join conference
			BeginAsyncProcess2(JoinOfficeConference);
		}

        /// <summary>
        /// Make operator sessio
        /// </summary>
        private void MakeOperatorSession()
        {
            EFS.EventLog.Log.LogInfo("MakeOperatorSession", this);

            // SAL
            EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "MakeOperatorSession", this);

            // customer is in, call agent
            EFS.RC.CC.Sessions.OfficeService.Operators.DialIn toDialInOperatorOfficeServiceSession = new EFS.RC.CC.Sessions.OfficeService.Operators.DialIn(operatorPresence, this);
            toDialInOperatorOfficeServiceSession.AsinkEstablishedDelegate.Register(OnOperatorDirectDialInSessionEstablished);
            toDialInOperatorOfficeServiceSession.BeginStartup();
        }

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		private void JoinOfficeConference()
		{
			// log
			EFS.EventLog.Log.LogInfo("JoinOfficeConference", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "JoinOfficeConference", this); 

			// get conference uri
			System.String conferenceUri = OfficeServiceConference.ConferenceUri;

			// get end point
			Microsoft.Rtc.Collaboration.ApplicationEndpoint applicationEndpoint =CustomersOfficeServiceEndPointApplication.CollaborationApplicationEndpoint;

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// log impersonation
				EFS.EventLog.Log.LogInfo(System.String.Format("Impersonate officeConversation {0}", ParticipantEndPointUri), this);

				// make office conversation
				officeConversation = new Microsoft.Rtc.Collaboration.Conversation(applicationEndpoint);
				officeConversation.Impersonate(ParticipantEndPointUri, null, DisplayUri);
				officeConversation.ApplicationContext = this;
				officeConversation.StateChanged += OnOfficeConversationStateChanged;

				// conference options
				Microsoft.Rtc.Collaboration.ConferenceJoinOptions conferenceJoinOptions = new Microsoft.Rtc.Collaboration.ConferenceJoinOptions();
				conferenceJoinOptions.JoinMode = Microsoft.Rtc.Collaboration.JoinMode.Default;

				// join
				officeConversation.ConferenceSession.BeginJoin(
					conferenceUri,
					conferenceJoinOptions,
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(EndJoinOfficeConference, null));
			}
		}

		/// <summary>
		/// End join
		/// </summary>
		/// <param name="result"></param>
		private void EndJoinOfficeConference(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("EndJoinOfficeConference", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "EndJoinOfficeConference", this);

			try
			{
				// end join
				officeConversation.ConferenceSession.EndJoin(ar);

				// make back2back call
                BeginAsyncProcess2(MakeB2BCall);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// <summary>
		/// Make back2back call for customer into conference
		/// </summary>
		private void MakeB2BCall()
		{
			EFS.EventLog.Log.LogInfo("MakeB2BCall", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "MakeB2BCall", this);

			// ~ commented for performance ~  EFS.EventLog.Log.LogExtended("Entering lock", new System.Diagnostics.StackTrace());
			lock (syncAccess)
			{
				// check status
				if (Status != SessionStatus.Started)
				{
					throw new System.InvalidOperationException("Invalid Status for current operation. Expected Status: Started");
				}

				// Make B2B
				officeConversationAudioVideoCall = new Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall(officeConversation);

				// make call
				officeConversationB2BCall = new Microsoft.Rtc.Collaboration.BackToBackCall(
						new Microsoft.Rtc.Collaboration.BackToBackCallSettings(inboundCustomerAudioVideoCall),
						new Microsoft.Rtc.Collaboration.BackToBackCallSettings(officeConversationAudioVideoCall));

				// start call
				officeConversationB2BCall.BeginEstablish(
					EFS.Common.Async.AsyncResult.StateDelegate.End,
					EFS.Common.Async.AsyncResult.StateDelegate.State(OnEndEstablishB2BCall));
			}
		}

		/// <summary>
		/// End B2B call
		/// </summary>
		/// <param name="ar"></param>
		private void OnEndEstablishB2BCall(System.IAsyncResult ar)
		{
			try
			{
				// end
				officeConversationB2BCall.EndEstablish(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}
		
		/// <summary>
		/// Called when participant end point added
		/// </summary>
		protected override void ParticipantEndPointAdded()
		{
			// call base
			base.ParticipantEndPointAdded();

			// start dial player
			BeginStartDialPlayer();

			// make operator session
			BeginAsyncProcess2(MakeOperatorSession);
		}

		/// <summary>
		/// Start-up complete
		/// </summary>
		/// <param name="ar"></param>
		private void OnStartupComplete(EFS.Common.Async.AsyncProcess ap)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnStartupComplete", this);

			try
			{
				// check
				ap.Throw();

				// log
				EFS.EventLog.Log.LogInfo("OnStartupComplete SUCCCESS", this);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

   		/// <summary>
		/// Established Delegate
		/// </summary>
		/// <param name="customerDialOutSession"></param>
        private void OnOperatorDirectDialInSessionEstablished(EFS.RC.CC.Sessions.OfficeService.IParticipant officeServiceSessionParticipant)
        {
            // log
            EFS.EventLog.Log.LogInfo("OnOperatorDirectDialInSessionEstablished", this);

            // check
            EFS.Common.Verify.Reference(officeServiceSessionParticipant, "officeServiceSessionParticipant");

            // cast
            EFS.RC.CC.Sessions.OfficeService.Operators.DialIn operatorSession = officeServiceSessionParticipant as EFS.RC.CC.Sessions.OfficeService.Operators.DialIn;

            // check
            EFS.Common.Verify.Reference(operatorSession, "operatorSession");

			// stop dial player
			BeginStopPlayers();
        }

		#endregion

		#region Runtime

		/// <summary>
		/// Outside call
		/// </summary>
		protected override Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall OutsideAudioVideoCall
		{
			get { return inboundCustomerAudioVideoCall; }
		}

		/// <summary>
		/// Inside call
		/// </summary>
		protected override Microsoft.Rtc.Collaboration.AudioVideo.AudioVideoCall InsideAudioVideoCall
		{
			get { return officeConversationAudioVideoCall; }
		}

		/// <summary>
		/// Office conversation
		/// </summary>
		protected override Microsoft.Rtc.Collaboration.Conversation OfficeConversation
		{
			get { return officeConversation; }
		}

		/// <summary>
		/// State changed for customer conersation
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnCustomerConversationStateChanged(object sender, Microsoft.Rtc.Signaling.StateChangedEventArgs<Microsoft.Rtc.Collaboration.ConversationState> args)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnCustomerConversationStateChanged", this);

			try
			{
				// SAL
				EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "OnCustomerConversationStateChanged", args.State, this);

				// check terminating
				if (args.State == Microsoft.Rtc.Collaboration.ConversationState.Terminating)
				{
					// log
					EFS.EventLog.Log.LogInfo("Shutting down - customer conversation is beyign terminated", this);

					// start 
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}	
		}

		/// <summary>
		/// State changed for office conersation
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		private void OnOfficeConversationStateChanged(object sender, Microsoft.Rtc.Signaling.StateChangedEventArgs<Microsoft.Rtc.Collaboration.ConversationState> args)
		{
			// log
			EFS.EventLog.Log.LogInfo("OnOfficeConversationStateChanged", this);

			try
			{
				// SAL
				EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "OnOfficeConversationStateChanged", args.State, this);

				// check terminating
				if (args.State == Microsoft.Rtc.Collaboration.ConversationState.Terminating)
				{
					// log
					EFS.EventLog.Log.LogInfo("Shutting down - office conversation is beyign terminated", this);

					// start 
					BeginShutdown();
				}
			}
			catch (System.Exception ex)
			{
				// handle ex
				HandleExceptionSafe(ex);
			}	
		}

		#endregion

		#region Shutdown

		/// <summary>
		/// <summary>
		/// Remove customer from office conference
		/// </summary>
		protected override void CleanupSession()
		{
			// log
			EFS.EventLog.Log.LogInfo("CleanupSession", this);

			// SAL
			EFS.RC.CC.Tools.Loggers.SAL.Add(ConversationGuid, "CleanupSession", this); 

			// make termination
			EFS.RC.CC.Tools.Functions.Termination termination = new EFS.RC.CC.Tools.Functions.Termination(this);

			// remove delegate
			inboundCustomerAudioVideoCall.Conversation.StateChanged -= OnCustomerConversationStateChanged;

			// terminate all
			termination.Terminate(officeConversationB2BCall, OnEndTerminateB2BCall);
			termination.Terminate(inboundCustomerAudioVideoCall, EndTerminateInboundCustomerAudioVideoCall);
		}

		/// End B2B
		/// </summary>
		/// <param name="ar"></param>
		private void OnEndTerminateB2BCall(System.IAsyncResult ar)
		{
			EFS.EventLog.Log.LogInfo("OnEndTerminateB2BCall", this);

			try
			{
				// end terminate
				officeConversationB2BCall.EndTerminate(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		/// End conversation
		/// </summary>
		/// <param name="ar"></param>
		private void EndTerminateInboundCustomerAudioVideoCall(System.IAsyncResult ar)
		{
			// log
			EFS.EventLog.Log.LogInfo("EndTerminateInboundCustomerAudioVideoCall", this);

			try
			{
				// remove delegate
				inboundCustomerAudioVideoCall.Conversation.StateChanged -= OnCustomerConversationStateChanged;

				// end terminate
				inboundCustomerAudioVideoCall.EndTerminate(ar);
			}
			catch (System.Exception ex)
			{
				// handle exception
				HandleExceptionSafe(ex);
			}
		}

		#endregion
	}
}
